package com.suntianci.stock.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.io.BaseEncoding;
import com.suntianci.stock.constant.PermissionConstant;
import com.suntianci.stock.constant.StockConstant;
import com.suntianci.stock.mapper.SysPermissionMapper;
import com.suntianci.stock.mapper.SysRoleMapper;
import com.suntianci.stock.mapper.SysUserMapper;
import com.suntianci.stock.mapper.SysUserRoleMapper;
import com.suntianci.stock.pojo.domain.UserInfo4SelectById;
import com.suntianci.stock.pojo.domain.PermissionDomain;
import com.suntianci.stock.pojo.domain.UserInfoDomain;
import com.suntianci.stock.pojo.entity.SysPermission;
import com.suntianci.stock.pojo.entity.SysUser;
import com.suntianci.stock.pojo.entity.SysUserRole;
import com.suntianci.stock.pojo.po.query.UserInfoQueryPo;
import com.suntianci.stock.security.utils.JwtTokenUtil;
import com.suntianci.stock.service.UserService;
import com.suntianci.stock.utils.IdWorker;
import com.suntianci.stock.utils.PermissionUtil;
import com.suntianci.stock.vo.req.*;
import com.suntianci.stock.vo.resp.LoginRespVo;
import com.suntianci.stock.vo.resp.PageResult;
import com.suntianci.stock.vo.resp.R;
import com.suntianci.stock.vo.resp.ResponseCode;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @ClassName UserServiceImpl
 * @Author 孙天赐
 * @Date 2024/12/24 19:42
 * @Description TODO: 用户服务接口实现类
 */
// @Slf4j注解: 在 Java 类中自动生成一个 org.slf4j.Logger 类型的日志记录器（Logger），简化了手动创建 Logger 实例的步骤。
@Service("userService")
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private SysRoleMapper sysRoleMapper;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    @Autowired
    private SysPermissionMapper sysPermissionMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * TODO: 根据用户名查询用户信息
     *
     * @param userName
     * @return
     */
    @Override
    public SysUser findUserInfoByUserName(String userName) {
        return sysUserMapper.findUserInfoByUserName(userName);
    }

    /**
     * TODO: 用户登录功能
     *
     * @param loginReqVo
     * @return
     */
    @Override
    public R<LoginRespVo> login(LoginReqVo loginReqVo) {
        //1.1 判断用户名和密码是否合法
        if (loginReqVo == null || StringUtils.isBlank(loginReqVo.getUsername()) || StringUtils.isBlank(loginReqVo.getPassword())) {
            return R.error(ResponseCode.DATA_ERROR);
        }

        //1.2 判断验证码是否合法
        if (loginReqVo.getCode() == null || loginReqVo.getSessionId() == null) {
            return R.error(ResponseCode.CHECK_CODE_ERROR);
        }

        //1.3 判断验证码是否正确（比较时，忽略大小写）
        //1.3.1 获取redis中保存的正确验证码
        String rightCode = (String) redisTemplate.opsForValue().get(StockConstant.CHECK_PREFIX + loginReqVo.getSessionId());
        //1.3.2 如果redis中该验证码不存在，表明会话过期
        if (rightCode == null) {
            return R.error(ResponseCode.CHECK_CODE_TIMEOUT);
        }
        //1.3.3判断输入的验证码与正确验证码是否匹配（忽略大小写）
        if (!rightCode.equalsIgnoreCase(loginReqVo.getCode())) {
            return R.error(ResponseCode.CHECK_CODE_ERROR);
        }

        //2.根据用户名查询用户信息，获得密码的密文
        SysUser user = sysUserMapper.findUserInfoByUserName(loginReqVo.getUsername());
        // 判断用户是否存在
        if (user == null) {
            return R.error(ResponseCode.ACCOUNT_NOT_EXISTS);
        }

        //3.使用密码匹配器匹配用户输入的密码和密文是否匹配
        boolean matches = passwordEncoder.matches(loginReqVo.getPassword(), user.getPassword());
        // 不匹配
        if (!matches) {
            return R.error(ResponseCode.USERNAME_OR_PASSWORD_ERROR);
        }

        // 匹配
        LoginRespVo respVo = new LoginRespVo();

        // 发现LoginRespVo和SysUser对象属性名称和类型一致，可以使用工具包（只有名称和类型都一致才可以使用）
        BeanUtils.copyProperties(user, respVo);

        // 4. 获取用户的权限集合
        List<SysPermission> userPermissionList = sysPermissionMapper.getAllPermissionByUserId(user.getId());

        // 5. 构建侧边栏权限树（type=1或2）
        List<PermissionDomain> result = new ArrayList<>();
        for (SysPermission sysPermission : userPermissionList) {
            if (sysPermission.getPid() == 0) {
                // 递归构建权限树
                PermissionDomain temp = PermissionUtil.getPermissionTreeWithDirectiryAndMenu(sysPermission, userPermissionList);
                result.add(temp);
            }
        }
        respVo.setMenus(result);

        // 6. 使用stream流获取按钮权限标识（获取权限集合中type=3的权限信息）
        List<String> permissions = userPermissionList.stream()
                .filter(item -> item.getType() == PermissionConstant.TYPE_BUTTON)
                .map((item) -> {
                    return item.getCode();
                })
                .collect(Collectors.toList());

        respVo.setPermissions(permissions);

        // 7. 用户的id和name信息经过base64编码，作为票据token
        // 7.1 获取编码前字符串："id:username"
        String token = String.valueOf(respVo.getId()) + ":" + respVo.getUsername();
        // 7.2 编码
        String accessToken = BaseEncoding.base64().encode(token.getBytes());
        respVo.setAccessToken(accessToken);

        //7.响应结果
        return R.ok(respVo);
    }

    /**
     * TODO: 生成图片验证码功能
     *
     * @return
     */
    @Override
    public R<Map> getCaptchaCode() {
        // 1.使用hutool工具类生成图片验证码
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(250, 40, 4, 8);
        // 设置背景颜色
        lineCaptcha.setBackground(Color.LIGHT_GRAY);
        // 自定义生成校验码的规则
//        lineCaptcha.setGenerator(new CodeGenerator() {
//            @Override
//            public String generate() {
//                // 自定义校验码生成的逻辑
//                return null;
//            }
//
//            @Override
//            public boolean verify(String s, String s1) {
//                // 匹配校验码逻辑
//                return false;
//            }
//        });
        // 1.1.获取校验码
        String checkCode = lineCaptcha.getCode();
        // 1.2.获取经过base64处理的图片数据
        String imageData = lineCaptcha.getImageBase64();
        // 2.生成sessionId 转化成string，避免前端精度丢失
        String sessionId = String.valueOf(idWorker.nextId());
        log.info("当前生成的图片校验码：{}，会话ID：{}", checkCode, sessionId);
        // 3.将sessionId作为key，校验码作为value保存在redis中。使用Redis模拟session的行为，通过过期时间设置
        // key的前面加了个"CK:"，表示业务前缀，方便后期查找redis中有多少数据和验证码业务相关
        redisTemplate.opsForValue().set(StockConstant.CHECK_PREFIX + sessionId, checkCode, 10, TimeUnit.MINUTES);
        // 4.组装数据
        Map<String, String> data = new HashMap<>();
        data.put("imageData", imageData);
        data.put("sessionId", sessionId);
        // 5.响应数据
        return R.ok(data);
    }

    /**
     * 分页查询全部用户信息
     *
     * @param usersInfoReqVo
     * @return
     */
    @Override
    public R<PageResult<UserInfoDomain>> getUsersInfoByPage(UsersInfoReqVo usersInfoReqVo) {
        // 1.获取分页数据
        // 1.1 当前页码
        Integer pageNum = Integer.parseInt(usersInfoReqVo.getPageNum());
        // 1.2 每页大小
        Integer pageSize = Integer.parseInt(usersInfoReqVo.getPageSize());
        // 2.设置分页参数，底层会拦截mybatis发送的sql，并动态追加limit语句实现分页
        PageHelper.startPage(pageNum, pageSize);
        // 3.获取其他参数，封装到common模块的类中
        UserInfoQueryPo userInfoQueryPo = UserInfoQueryPo.builder()
                .username(usersInfoReqVo.getUsername())
                .nickName(usersInfoReqVo.getNickName())
                .startTime(usersInfoReqVo.getStartTime())
                .endTime(usersInfoReqVo.getEndTime()).build();
        // 4.调用mapper获取数据
        List<UserInfoDomain> result = sysUserMapper.findAllUsersInfo(userInfoQueryPo);
        // 5.搭建PageResult对象
        // 5.1 将查询的Page对象封装到PageInfo下就可以获取分页的各种数据
        PageInfo<UserInfoDomain> pageInfo = new PageInfo<>(result);
        // 5.2 将pageInfo对象封装到pageResult下（使用自己定义的构造函数实现的封装）
        // 因为PageInfo包含了丰富的分页信息，而部分分页信息是前端不需要的
        PageResult<UserInfoDomain> pageResult = new PageResult<>(pageInfo);
        return R.ok(pageResult);
    }

    /**
     * 添加用户信息
     *
     * @param addUserReqVo
     * @return
     */
    @Override
    public R<Void> addUser(AddUserReqVo addUserReqVo, HttpServletRequest httpServletRequest) {
        // 获取当前请求人信息（编码后的Token）
        String accessToken = httpServletRequest.getHeader(StockConstant.TOKEN_HEADER);
        // 解析tokenStr,获取用户详情信息
        Claims claims = JwtTokenUtil.checkJWT(accessToken);
        //token字符串失效的情况
        if (claims==null) {
            //说明 票据解析出现异常，票据就失效了
            return R.error(ResponseCode.ERROR);
        }
        // 解析出用户名
        String userName = JwtTokenUtil.getUsername(accessToken);
        // 获取用户名对应id
        Long createId = sysUserMapper.findUserInfoByUserName(userName).getId();
        // 创建对象，用于添加数据
        SysUser sysUser = SysUser.builder()
                .id(idWorker.nextId())
                .username(addUserReqVo.getUsername())
                .password(passwordEncoder.encode(addUserReqVo.getPassword()))
                .phone(addUserReqVo.getPhone())
                .realName(addUserReqVo.getRealName())
                .nickName(addUserReqVo.getNickName())
                .email(addUserReqVo.getEmail())
                .status(Integer.parseInt(addUserReqVo.getStatus()))
                .sex(Integer.parseInt(addUserReqVo.getSex()))
                .deleted(1)
                .createId(createId)
                .createWhere(Integer.parseInt(addUserReqVo.getCreateWhere()))
                .createTime(DateTime.now().toDate())
                .build();
        // 调用mapper向数据库添加用户
        int row = sysUserMapper.insert(sysUser);
        if (row > 0) {
            return R.ok(ResponseCode.SUCCESS);
        }
        return R.error(ResponseCode.ERROR);
    }

    /**
     * 获取用户具有的角色信息，以及所有角色信息
     *
     * @param userId
     * @return
     */
    @Override
    public R<Map<String, List>> getUserRoleAndAllRoles(String userId) {
        // 将userId转化为Long类型
        Long newUserId = Long.valueOf(userId);
        // 获取用户的全部角色
        List userRoles = sysUserRoleMapper.getRolesByUserId(newUserId);
        // 获取全部的角色信息
        List allRoles = sysRoleMapper.selectAllRoles();
        // 封装数据
        Map<String, List> result = new HashMap<>();
        result.put("ownRoleIds", userRoles);
        result.put("allRole", allRoles);
        return R.ok(result);
    }

    /**
     * 更新用户角色信息
     *
     * @param updateUserRolesReqVo
     * @return
     */
    @Override
    public R<Void> updateUserRoles(UpdateUserRolesReqVo updateUserRolesReqVo, HttpServletRequest httpServletRequest) {
        // 获取当前请求人信息（编码后的Token）
        String accessToken = httpServletRequest.getHeader(StockConstant.TOKEN_HEADER);
        // 解析tokenStr,获取用户详情信息
        Claims claims = JwtTokenUtil.checkJWT(accessToken);
        //token字符串失效的情况
        if (claims==null) {
            //说明 票据解析出现异常，票据就失效了
            return R.error(ResponseCode.ERROR);
        }
        // 解析出用户名
        String userName = JwtTokenUtil.getUsername(accessToken);
        // 获取用户名对应id
        Long updateId = sysUserMapper.findUserInfoByUserName(userName).getId();

        // 更新用户信息（更新人id）
        Long userId = updateUserRolesReqVo.getUserId();
        SysUser sysUser = SysUser.builder()
                .id(userId)
                .updateId(updateId).build();
        sysUserMapper.updateByPrimaryKeySelective(sysUser);

        // 删除该用户的角色
        sysUserRoleMapper.deleteByUserId(userId);
        // 新增该用户的角色
        Date nowTime = DateTime.now().toDate();
        for (Long roleId : updateUserRolesReqVo.getRoleIds()) {
            // 新建对象，用于插入数据
            SysUserRole temp = SysUserRole.builder()
                    .id(idWorker.nextId())
                    .userId(userId)
                    .roleId(roleId)
                    .createTime(nowTime).build();
            // 插入数据
            int insertRow = sysUserRoleMapper.insert(temp);
            if (insertRow == 0) {
                return R.error(ResponseCode.ERROR);
            }
        }
        return R.ok(ResponseCode.SUCCESS);
    }

    /**
     * 批量删除用户信息
     *
     * @param userIds
     * @return
     */
    @Override
    public R<Void> batchDeleteUser(List<Long> userIds) {
        // 删除用户信息
        int row = sysUserMapper.batchDeleteUser(userIds);
        if (row > 0) {
            // 注意，也要删除用户角色表信息
            sysUserRoleMapper.batchDeleteByUserId(userIds);
            return R.ok(ResponseCode.SUCCESS);
        }
        return R.error(ResponseCode.ERROR);
    }

    /**
     * 根据用户id查询用户信息
     *
     * @param userId
     * @return
     */
    @Override
    public R<UserInfo4SelectById> getUserInfoById(String userId) {
        // 获取当前用户全部数据
        SysUser allInfo = sysUserMapper.selectByPrimaryKey(Long.valueOf(userId));
        if (allInfo == null) {
            return R.error(ResponseCode.NO_RESPONSE_DATA);
        }
        // 提取部分数据
        // 发现LoginRespVo和SysUser对象属性名称和类型一致，可以使用工具包（只有名称和类型都一致才可以使用）
        UserInfo4SelectById info = new UserInfo4SelectById();
        BeanUtils.copyProperties(allInfo, info);
        if (info == null) {
            return R.error(ResponseCode.ERROR);
        }
        return R.ok(info);
    }

    /**
     * 根据id更新用户基本信息
     *
     * @param updateUserReqVo
     * @return
     */
    @Override
    public R<Void> updateUserById(UpdateUserReqVo updateUserReqVo, HttpServletRequest httpServletRequest) {
        // 获取当前请求人信息（编码后的Token）
        String accessToken = httpServletRequest.getHeader(StockConstant.TOKEN_HEADER);
        // 解析tokenStr,获取用户详情信息
        Claims claims = JwtTokenUtil.checkJWT(accessToken);
        //token字符串失效的情况
        if (claims==null) {
            //说明 票据解析出现异常，票据就失效了
            return R.error(ResponseCode.ERROR);
        }
        // 解析出用户名
        String userName = JwtTokenUtil.getUsername(accessToken);
        // 获取用户名对应id
        Long updateId = sysUserMapper.findUserInfoByUserName(userName).getId();

        // 获取部分数据
        String sexStr = updateUserReqVo.getSex();
        String createWhereStr = updateUserReqVo.getCreateWhere();
        String statusStr = updateUserReqVo.getStatus();
        // 处理这部分数据(string -> integer)
        Integer sex = sexStr == null ? null : Integer.parseInt(sexStr);
        Integer createWhere = createWhereStr == null ? null : Integer.parseInt(createWhereStr);
        Integer status = statusStr == null ? null : Integer.parseInt(statusStr);
        // 新建用户对象，用于向数据库中更新数据
        SysUser temp = SysUser.builder()
                .id(updateUserReqVo.getId())
                .username(updateUserReqVo.getUsername())
                .phone(updateUserReqVo.getPhone())
                .email(updateUserReqVo.getEmail())
                .nickName(updateUserReqVo.getNickName())
                .realName(updateUserReqVo.getRealName())
                .sex(sex)
                .updateId(updateId)
                .createWhere(createWhere)
                .status(status)
                .updateTime(DateTime.now().toDate()).build();
        // 更新数据
        sysUserMapper.updateByPrimaryKeySelective(temp);
        return R.ok(ResponseCode.SUCCESS);

    }


}
